package com.shopoo.oauth.config;

import java.security.KeyPair;
import java.util.ArrayList;
import java.util.List;

import javax.annotation.Resource;

import com.alibaba.druid.pool.DruidDataSource;
import com.shopoo.oauth.infrastructure.config.JksProperties;
import com.shopoo.oauth.infrastructure.config.JwtTokenEnhancer;
import com.shopoo.oauth.wechat.granter.CustomTokenGranter;
import com.shopoo.oauth.wechat.service.CustomUserDetailsService;
import lombok.extern.slf4j.Slf4j;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.FileSystemResource;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.oauth2.config.annotation.configurers.ClientDetailsServiceConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configuration.AuthorizationServerConfigurerAdapter;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableAuthorizationServer;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerEndpointsConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerSecurityConfigurer;
import org.springframework.security.oauth2.provider.ClientDetailsService;
import org.springframework.security.oauth2.provider.CompositeTokenGranter;
import org.springframework.security.oauth2.provider.OAuth2RequestFactory;
import org.springframework.security.oauth2.provider.TokenGranter;
import org.springframework.security.oauth2.provider.client.ClientCredentialsTokenGranter;
import org.springframework.security.oauth2.provider.code.AuthorizationCodeServices;
import org.springframework.security.oauth2.provider.code.AuthorizationCodeTokenGranter;
import org.springframework.security.oauth2.provider.implicit.ImplicitTokenGranter;
import org.springframework.security.oauth2.provider.password.ResourceOwnerPasswordTokenGranter;
import org.springframework.security.oauth2.provider.refresh.RefreshTokenGranter;
import org.springframework.security.oauth2.provider.token.AuthorizationServerTokenServices;
import org.springframework.security.oauth2.provider.token.TokenEnhancer;
import org.springframework.security.oauth2.provider.token.TokenEnhancerChain;
import org.springframework.security.oauth2.provider.token.TokenStore;
import org.springframework.security.oauth2.provider.token.store.JwtAccessTokenConverter;
import org.springframework.security.oauth2.provider.token.store.JwtTokenStore;
import org.springframework.security.oauth2.provider.token.store.KeyStoreKeyFactory;

/**
 * @Description 认证服务器配置
 * @Date 2020/12/11 10:42 上午
 * @Author <a href="mailto:android_li@sina.cn">Joe</a>
 **/
@Slf4j
@Configuration
@EnableAuthorizationServer
public class Oauth2ServerConfig extends AuthorizationServerConfigurerAdapter {

    @Resource
    private AuthenticationManager authenticationManager;

    @Resource
    private JwtTokenEnhancer jwtTokenEnhancer;

    @Resource
    private DruidDataSource dataSource;

    @Resource
    private UserDetailsService userDetailsService;

    @Resource
    private CustomUserDetailsService customUserDetailsService;

    @Bean
    public BCryptPasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }

    @Bean
    public TokenStore tokenStore() {
        return new JwtTokenStore(accessTokenConverter());
    }

    @Resource
    private JksProperties jksProperties;

    @Override
    public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
        List<TokenGranter> tokenGranters = getTokenGranters(endpoints.getAuthorizationCodeServices(), endpoints.getTokenStore(), endpoints.getTokenServices(), endpoints.getClientDetailsService(), endpoints.getOAuth2RequestFactory());
        try {
            TokenEnhancerChain enhancerChain = new TokenEnhancerChain();
            List<TokenEnhancer> delegates = new ArrayList<>();
            delegates.add(jwtTokenEnhancer);
            delegates.add(accessTokenConverter());
            enhancerChain.setTokenEnhancers(delegates); //配置JWT的内容增强器
            endpoints
                    //指定token存储位置
                    .tokenStore(tokenStore())
                    .tokenGranter(new CompositeTokenGranter(tokenGranters))
                    // 配置JwtAccessToken转换器
                    .tokenEnhancer(enhancerChain)
                    //指定认证管理器
                    .authenticationManager(authenticationManager)
                    //这里一定要配置userDetailsService，不然刷新token会出错，refresh_token
                    .userDetailsService(userDetailsService)
                    // 配置access_token使用的转换
                    .accessTokenConverter(accessTokenConverter());
        } catch (Exception e) {
            log.error("授权异常:{}", e);
        }
    }

    @Bean
    public JwtAccessTokenConverter accessTokenConverter() {
        JwtAccessTokenConverter jwtAccessTokenConverter = new JwtAccessTokenConverter();
        jwtAccessTokenConverter.setKeyPair(keyPair());
        return jwtAccessTokenConverter;
    }

    @Bean
    public KeyPair keyPair() {
        //从classpath下的证书中获取秘钥对
        KeyStoreKeyFactory keyStoreKeyFactory = new KeyStoreKeyFactory(new FileSystemResource(jksProperties.getPath()), jksProperties.getPassword().toCharArray());
        return keyStoreKeyFactory.getKeyPair(jksProperties.getAlias(), jksProperties.getPassword().toCharArray());
    }

    @Override
    public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
        security
                .tokenKeyAccess("permitAll()")
                .checkTokenAccess("isAuthenticated()")
                .passwordEncoder(passwordEncoder())
                .allowFormAuthenticationForClients();
    }

    @Override
    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
        clients
                .jdbc(dataSource)
                .passwordEncoder(passwordEncoder());
    }

    /**
     * @Description: 获取所有支持的授权模式
     * @Author: limy66
     * @Date:   2021/1/27 17:14
     * @Param:  [authorizationCodeServices, tokenStore, tokenServices, clientDetailsService, requestFactory]
     * @Return: java.util.List<org.springframework.security.oauth2.provider.TokenGranter>
     */
    private List<TokenGranter> getTokenGranters(AuthorizationCodeServices authorizationCodeServices, TokenStore tokenStore, AuthorizationServerTokenServices tokenServices, ClientDetailsService clientDetailsService, OAuth2RequestFactory requestFactory) {
        List<TokenGranter> tokenGranters = new ArrayList<>();
        //添加授权码模式授权
        tokenGranters.add(new AuthorizationCodeTokenGranter(tokenServices,
                authorizationCodeServices, clientDetailsService, requestFactory));
        //添加刷新Token授权模式
        tokenGranters.add(new RefreshTokenGranter(tokenServices, clientDetailsService, requestFactory));
        //添加简化授权模式
        ImplicitTokenGranter implicit = new ImplicitTokenGranter(tokenServices, clientDetailsService,
                requestFactory);
        tokenGranters.add(implicit);
        //添加客户端授权模式
        tokenGranters.add(
                new ClientCredentialsTokenGranter(tokenServices, clientDetailsService, requestFactory));
        if (authenticationManager != null) {
            //添加密码授权模式
            tokenGranters.add(new ResourceOwnerPasswordTokenGranter(authenticationManager,
                    tokenServices, clientDetailsService, requestFactory));
        }
        //添加自定义授权模式
        tokenGranters.add(new CustomTokenGranter(customUserDetailsService, tokenServices, clientDetailsService, requestFactory));
        return tokenGranters;
    }
}
